#include "degfx/bitmap.h"

#define MXMODULE_FGETW
#define MXMODULE_BITPIXEL

#include <assert.h>
#include <stdio.h>

static void mx__tga_read0(MX_BITMAP_ITER iter, FILE * f)
{
	(void) iter;
	(void) f;
}

static void mx__tga_read8(MX_BITMAP_ITER iter, FILE * f)
{
	const unsigned cindex = fgetc(f);

	*iter = cindex;
}

static void mx__tga_read8gray(MX_BITMAP_ITER iter, FILE * f)
{
	const unsigned cindex = fgetc(f);

	*iter = MXRGB(cindex, cindex, cindex);
}

static void mx__tga_read16(MX_BITMAP_ITER iter, FILE * f)
{
	const unsigned value = mx_fgetw(f);
	const unsigned b = (value >> 7) & 0xF8;
	const unsigned g = (value >> 2) & 0xF8;
	const unsigned r = (value & 0x1F) << 3;

	*iter = MXRGB(r, g, b);
}

static void mx__tga_read24(MX_BITMAP_ITER iter, FILE * f)
{
	const unsigned b = fgetc(f);
	const unsigned g = fgetc(f);
	const unsigned r = fgetc(f);

	*iter = MXRGB(r, g, b);
}

static void mx__tga_read32(MX_BITMAP_ITER iter, FILE * f)
{
	const unsigned b = fgetc(f);
	const unsigned g = fgetc(f);
	const unsigned r = fgetc(f);
	const unsigned a = fgetc(f);

	*iter = MXRGBT(r, g, b, 0xff - a);
}

typedef void (*MX__TGA_READER) (MX_BITMAP_ITER iter, FILE * f);

static void mx__tga_rle_read(MX_BITMAP_ITER b, int w, FILE * f, MX__TGA_READER reader)
{
	int c = 0;

	do {
		int count = fgetc(f);

		if (count & 0x80) {
			MX_PIXEL value;

			/* run-length packet */
			count = (count & 0x7F) + 1;
			c += count;
			reader(&value, f);

			while (count--)
				*b++ = value;

		} else {
			/* raw packet */
			int n = 0;

			count++;
			c += count;

			while (n < count)
				reader(b++, f);
		}
	}
	while (c < w);
}

static void mx__tga_raw_read(MX_BITMAP_ITER b, int w, FILE * f, MX__TGA_READER reader)
{
	while (w--)
		reader(b++, f);
}

MX_BITMAP *mx_bitmap_tga(const char *filename)
{
	/** Source taken from the TGA reader from Allegro and extensively modified. */
	unsigned char image_id[256], image_palette[256][4];
	unsigned char id_length, palette_type, image_type, palette_entry_size;
	unsigned char bpp, descriptor_bits;
	unsigned int palette_colors;
	unsigned int image_width, image_height;
	unsigned int i, y;
	int compressed;
	FILE *f;
	MX_BITMAP *bmp = 0;
	MX__TGA_READER reader;

	assert(filename);

	f = fopen(filename, "rb");
	if (!f)
		return 0;

	id_length = fgetc(f);
	palette_type = fgetc(f);
	image_type = fgetc(f);
	mx_fgetw(f);				/* first_color */
	palette_colors = mx_fgetw(f);
	palette_entry_size = fgetc(f);
	mx_fgetw(f);				/* left */
	mx_fgetw(f);				/* top */
	image_width = mx_fgetw(f);
	image_height = mx_fgetw(f);
	bpp = fgetc(f);
	descriptor_bits = fgetc(f);

	fread(image_id, id_length, 1, f);

	for (i = 0; i < palette_colors; i++) {

		switch (palette_entry_size) {

		case 16:
			{
				unsigned int c = mx_fgetw(f);

				image_palette[i][0] = (c & 0x1F) << 3;
				image_palette[i][1] = (c >> 2) & 0xF8;
				image_palette[i][2] = (c >> 7) & 0xF8;
				image_palette[i][3] = 0;
				break;
			}

		case 24:
			image_palette[i][0] = fgetc(f);
			image_palette[i][1] = fgetc(f);
			image_palette[i][2] = fgetc(f);
			image_palette[i][3] = 0;
			break;

		case 32:
			image_palette[i][0] = fgetc(f);
			image_palette[i][1] = fgetc(f);
			image_palette[i][2] = fgetc(f);
			image_palette[i][3] = fgetc(f);
			break;

		default:
			goto done;
		}
	}

	/* Image type: * 0 = no image data * 1 = color mapped * 2 = true color *
	   3 = grayscale * Bit 4 means data is RLE compressed */
	compressed = (image_type & 0x08);
	image_type &= 0x03;

	switch (image_type) {

	case 0:
		/* no image data */
		reader = mx__tga_read0;
		break;

	case 1:
		/* paletted image */
		if ((palette_type != 1) || (bpp != 8))
			goto done;

		reader = mx__tga_read8;
		break;

	case 2:
		/* truecolor image */
		if ((bpp == 15) || (bpp == 16))
			reader = mx__tga_read16;

		else if (bpp == 24)
			reader = mx__tga_read24;

		else if (bpp == 32)
			reader = mx__tga_read32;

		else
			goto done;

		break;

	case 3:
		/* grayscale image */
		if (bpp != 8)
			goto done;

		reader = mx__tga_read8gray;
		break;

	default:
		goto done;
	}

	/* Create the bitmap */
	bmp = mx_bitmap(image_width - 1, image_height - 1);
	if (!bmp)
		goto done;
	MXDEBUG_ALLOCTAG(bmp);

	/* Read in the image data rows */
	for (y = image_height; y; y--) {
		unsigned int yc = (descriptor_bits & 0x20) ? image_height - y : y - 1;
		MX_BITMAP_ITER iter = mx_bitmap_iter(bmp, 0, yc);

		if (compressed)
			mx__tga_rle_read(iter, image_width, f, reader);
		else
			mx__tga_raw_read(iter, image_width, f, reader);
	}

	/* If image has a palette, change the colors to RGB */
	if (palette_colors) {
		MX_BITMAP_ITER start = mx_bitmap_begin(bmp);
		const MX_BITMAP_ITER end = mx_bitmap_end(bmp);

		while (start != end) {
			const unsigned char r = image_palette[*start][0];
			const unsigned char g = image_palette[*start][1];
			const unsigned char b = image_palette[*start][2];
			const unsigned char a = image_palette[*start][3];

			*start++ = MXRGBT(r, g, b, 0xff - a);
		}
	}

  done:
	fclose(f);
	return bmp;
}
